package top.wecreate.snet;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.Random;

import android.text.TextUtils;
import android.util.Log;

abstract class SnetConnectionRunnable implements Runnable {

	public static final String GET = "GET";
	public static final String POST = "POST";

	protected static final String TAG = "SnetConnetion";

	private String method;
	private String url;
	private OnConnectionResultListener resultListener;

	private HttpURLConnection connection;
	private Map<String, String> headers;
	private Map<String, String> params;
	private boolean needCancel = false;

	public SnetConnectionRunnable(String method, String url,
			OnConnectionResultListener listener) {
		this.method = method;
		this.url = url;
		this.resultListener = listener;
	}

	public void setHeaders(Map<String, String> headers) {
		this.headers = headers;
	}

	public void setParams(Map<String, String> params) {
		this.params = params;
	}

	public interface OnConnectionResultListener {
		public void onResponse(int responseCode,
				Map<String, List<String>> headers, byte[] responseBody);

		public void onException(Exception e);
	}

	@Override
	public void run() {
		if (TextUtils.isEmpty(url)) {
			sendExceptionResult(new Exception("请求地址为空"));
			return;
		}

		if (needCancel) {
			// 取消请求
			if (SnetConfig.getInstance().isShowLogMode()) {
				Log.w(TAG, "Cancel before run, url : " + url);
			}
			return;
		}

		if (SnetConfig.getInstance().isUseLocalTest()) {
			boolean showTestException = new Random().nextBoolean();
			if (showTestException) {
				Exception exception = getTestException();
				sendExceptionResult(exception);
			} else {
				SnetLocalTestResponse response = getTestResponse();
				if (response == null) {
					sendExceptionResult(new NullPointerException(
							"Test response is NULL"));
				} else {
					sendResponseResult(response.getCode(),
							response.getHeader(), response.getBody());
				}
			}
			return;
		}

		if (method.equals(GET)) {
			runGetConnection();
		} else if (method.equals(POST)) {
			runPostConnection();
		} else {

		}

	}

	public void cancel() {
		needCancel = true;
	}

	protected abstract Exception getTestException();

	protected abstract SnetLocalTestResponse getTestResponse();

	// Post请求的url，与get不同的是不需要带参数
	private void runPostConnection() {
		try {
			URL postUrl = new URL(url);

			if (SnetConfig.getInstance().isShowLogMode()) {
				Log.d(TAG, "Post url : " + postUrl.toString() + ", params = "
						+ encodeParams());
			}

			connection = (HttpURLConnection) postUrl.openConnection();

			// setting
			SnetConfig snetConfig = SnetConfig.getInstance();
			connection.setConnectTimeout(snetConfig.getConnectionTimeout());
			connection.setReadTimeout(snetConfig.getReadTimeout());
			connection.setInstanceFollowRedirects(snetConfig
					.isFollowRedirects());
			connection.setDoOutput(true); // 设置是否向connection输出，因为这个是post请求，参数要放在http正文内，因此需要设为true
			connection.setRequestMethod(POST);
			connection.setUseCaches(false);// Post 请求不能使用缓存

			if (headers != null && !headers.isEmpty()) {
				for (Map.Entry<String, String> header : headers.entrySet()) {
					connection.setRequestProperty(header.getKey(),
							header.getValue());
				}
			}

			connection.setRequestProperty(
					"Content-Type",
					"application/x-www-form-urlencoded;charset="
							+ snetConfig.getCharSet());
			// 连接，从postUrl.openConnection()至此的配置必须要在connect之前完成，
			// 要注意的是connection.getOutputStream会隐含的进行connect。
			connection.connect();

			// 在提交请求参数之前，再次检查一下是否需要取消请求。
			if (needCancel) {
				connection.disconnect();

				if (SnetConfig.getInstance().isShowLogMode()) {
					Log.w(TAG,
							"Cancel before connection.getOutputStream(), url : "
									+ postUrl);
				}

				return;
			}

			if (params != null && !params.isEmpty()) {
				String params = encodeParams();
				if (params != null) {
					DataOutputStream out = new DataOutputStream(
							connection.getOutputStream());
					out.writeBytes(params);// 正文，正文内容其实跟get的URL中'?'后的参数字符串一致
					out.flush();
					out.close(); // flush and close
				}
			}

			// 在获取网络数据之前，再次检查一下是否需要取消请求。
			if (needCancel) {
				connection.disconnect();

				if (SnetConfig.getInstance().isShowLogMode()) {
					Log.w(TAG,
							"Cancel before connection.getInputStream(), url : "
									+ postUrl);
				}

				return;
			}

			// Content
			InputStream is = connection.getInputStream();
			byte[] response = toByteArray(is);
			is.close();
			// 在获取网络数据之前，再次检查一下是否需要取消请求。
			if (needCancel) {
				connection.disconnect();

				if (SnetConfig.getInstance().isShowLogMode()) {
					Log.w(TAG,
							"Cancel after connection.getInputStream(), url : "
									+ postUrl);
				}

				return;
			}
			sendResponseResult(connection.getResponseCode(),
					connection.getHeaderFields(), response);
		} catch (IOException e) {
			// e.printStackTrace();
			sendExceptionResult(e);
			return;
		} finally {
			if (connection != null) {
				connection.disconnect();
			}
		}
	}

	private String encodeParams() {
		if (params == null || params.isEmpty()) {
			return null;
		}

		String charSet = SnetConfig.getInstance().getCharSet();
		StringBuilder builder = new StringBuilder();
		for (String paramKey : params.keySet()) {
			try {
				// 拼凑get请求的URL字串，使用URLEncoder.encode对特殊和不可见字符进行编码
				String paramValue = URLEncoder.encode(params.get(paramKey),
						charSet);

				builder.append(paramKey).append("=").append(paramValue)
						.append("&");
			} catch (UnsupportedEncodingException e) {
				// e.printStackTrace();
				sendExceptionResult(e);
				return null;
			}
		}
		String content = builder.substring(0, builder.length() - 1);
		return content;
	}

	private void runGetConnection() {
		// http://www.blogjava.net/jjshcc/archive/2010/07/21/326762.html

		String finalUrl = url;
		if (params != null && !params.isEmpty()) {
			String encodeParams = encodeParams();
			if (encodeParams == null) {
				return;
			}

			finalUrl += "?" + encodeParams;
		}

		if (SnetConfig.getInstance().isShowLogMode()) {
			Log.d(TAG, "Get url : " + finalUrl);
		}

		if (TextUtils.isEmpty(finalUrl)) {
			return;
		}

		try {
			URL getUrl = new URL(finalUrl);
			connection = (HttpURLConnection) getUrl.openConnection();

			// Settings
			SnetConfig snetConfig = SnetConfig.getInstance();
			connection.setConnectTimeout(snetConfig.getConnectionTimeout());
			connection.setReadTimeout(snetConfig.getReadTimeout());
			connection.setUseCaches(snetConfig.isUseCaches());
			connection.setInstanceFollowRedirects(snetConfig
					.isFollowRedirects());
			connection.setRequestMethod(GET);
			// Headers
			if (headers != null && !headers.isEmpty()) {
				for (Map.Entry<String, String> header : headers.entrySet()) {
					connection.setRequestProperty(header.getKey(),
							header.getValue());
				}
			}
			// charset
			connection.setRequestProperty(
					"Content-Type",
					"application/x-www-form-urlencoded;charset="
							+ snetConfig.getCharSet());
			connection.connect();

			// 在获取网络数据之前，再次检查一下是否需要取消请求。
			if (needCancel) {
				connection.disconnect();

				if (SnetConfig.getInstance().isShowLogMode()) {
					Log.w(TAG,
							"Cancel before connection.getInputStream(), url : "
									+ finalUrl);
				}

				return;
			}

			// connect
			InputStream is = connection.getInputStream();
			byte[] response = toByteArray(is);
			is.close();

			// 在获取网络数据之后，再次检查一下是否需要取消请求。
			if (needCancel) {
				connection.disconnect();

				if (SnetConfig.getInstance().isShowLogMode()) {
					Log.w(TAG,
							"Cancel after connection.getInputStream(), url : "
									+ finalUrl);
				}

				return;
			}

			sendResponseResult(connection.getResponseCode(),
					connection.getHeaderFields(), response);

		} catch (MalformedURLException e) {
			// e.printStackTrace();
			sendExceptionResult(e);
			return;
		} catch (IOException e) {
			// e.printStackTrace();
			sendExceptionResult(e);
			return;
		} finally {
			if (connection != null) {
				connection.disconnect();
			}
		}

	}

	public static byte[] toByteArray(InputStream input) throws IOException {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		byte[] buffer = new byte[4096];
		int n = 0;
		while (-1 != (n = input.read(buffer))) {
			output.write(buffer, 0, n);
		}
		return output.toByteArray();
	}

	private void sendExceptionResult(Exception e) {
		if (resultListener != null) {
			resultListener.onException(e);
		}
	}

	private void sendResponseResult(int responseCode,
			Map<String, List<String>> headerFields, byte[] responseBody) {
		if (resultListener != null) {
			resultListener.onResponse(responseCode, headerFields, responseBody);
		}
	}

}
